package com.zy.datareceiver.core.utils;

import com.zy.datareceiver.core.cast.ConCast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>KNN算法实现，其核心思想是 取出离目标点最近的K（K为基数）个点，如果这K个点中
 * 存在的各个属性统计结果中最多的属性分类即为目标点的属性</p>
 * */
public class KNN extends Thread{
    private static Logger log = LoggerFactory.getLogger(KNN.class);

    private static Double getAvgDistance(List<Double> arrays){
        if (arrays == null || arrays.isEmpty()){
            throw new IllegalArgumentException("传入的数据不能为空");
        }
        //大于20就不便是用该方法，会存在大量计算
        if (arrays.size() > 20){
            log.warn("计算KNN的list数据太多，不推荐采用KNN，接下来可能需要等待一会儿");
        }
        List<Double> distance  = new ArrayList<>();
        for (int i = 0; i < arrays.size(); i++) {
            for (int j = 0; j < arrays.size() && j < i; j++) {
                Double distan = arrays.get(i) - arrays.get(j);
                distance.add(distan);
            }
        }
        Double destance = distance.stream().mapToDouble(Double::doubleValue).average().getAsDouble();
        return destance;
    }

    /**
     * 在计算原来样本点的时候，需要所有样本点进行两两组合得到距离的平均数据，这样会存在的问题：
     * 当数据量较大的时候，两两组合会存在很大的cpu和时间消耗，再此推荐传入的数组集合小于20,
     * 如果大于20，会进行警告，但是程序会正常运行
     * */
    private static Double getAvgDistanceOfTarget(List<Double> arrays, Double target){
        if (arrays == null || arrays.isEmpty()){
            throw new IllegalArgumentException("传入的数据不能为空");
        }
        //大于10就不便是用该方法，会存在大量计算
        if (arrays.size() > 20){
            log.warn("计算KNN的list数据太多，不推荐采用KNN，接下来可能需要等待一会儿");
        }
        List<Double> distance  = new ArrayList<>();
        for (int i = 0; i < arrays.size(); i++) {
            Double distan = arrays.get(i) - target;
            distance.add(distan);
        }
        Double destance = distance.stream().mapToDouble(Double::doubleValue).average().getAsDouble();
        return destance;
    }


    /**
     * 先计算出原来集合中所有元素两两之间的距离的平均值，再计算出目标点与原来的各个样本点之间的距离平均值，
     * 如果目标平均值大于原样本的平均值的 {@link ConCast.KNN#THROULD_VALUE } 倍，则认为是异常数据
     * */
    public static boolean isExceptionData(List<Double> old, Double target){
        try{
            Double sourceAvg = getAvgDistance(old);
            Double targetAvg = getAvgDistanceOfTarget(old,target);
            if (targetAvg > sourceAvg * ConCast.KNN.THROULD_VALUE){
                return true;
            }
            return false;
        }catch (IllegalArgumentException e){
            log.error("KNN 计算是出现异常 {}",e.getMessage());
            return false;
        }

    }
}
